package service

import (
	"context"
	"fmt"
	"iris-wms/apps/admin/web/dto/din"
	"iris-wms/apps/admin/web/dto/dout"
	"iris-wms/common/model"
	"iris-wms/common/pkg/db"
	"iris-wms/common/pkg/db/dao"
	"iris-wms/common/tools"
	"strconv"

	"gorm.io/gen"
	"gorm.io/gorm"
)

var _ AdminUserService = (*adminUserService)(nil)

type AdminUserService interface {
	Create(*din.AdminUserIn) error
	Update(*din.AdminUserIn) error
	DeleteByIdList([]uint) error
	GetById(uint) (*dout.AdminUserOut, error)
	GetByUsername(string) (*model.AdminUser, error)
	ResetPassword(*din.ResetPassword) error
	GetTable(din.AdminUserTableIn) ([]*dout.AdminUserOut, int64, error)
	GetList() ([]*dout.AdminUserOut, error)
}

func NewAdminlUserService() *adminUserService {
	return &adminUserService{}
}

type adminUserService struct{}

func (impl *adminUserService) Create(in *din.AdminUserIn) (err error) {
	err = db.GDB1.Transaction(func(tx *gorm.DB) error {
		if err = tx.Create(&in).Error; err != nil {
			return err
		}
		if len(in.Roles) > 0 {
			casbins := make([]*model.AdminRule, 0)
			for _, v := range in.Roles {
				casbins = append(casbins, &model.AdminRule{
					Ptype: "g",
					V0:    strconv.Itoa(int(in.ID)),
					V1:    strconv.FormatUint(uint64(v), 10),
					V2:    tools.GConfig.App.Casbin.Tenant,
				})
			}
			if err = tx.CreateInBatches(casbins, 100).Error; err != nil {
				return err
			}
		}
		return nil
	})
	return
}

func (impl *adminUserService) Update(in *din.AdminUserIn) (err error) {
	err = db.GDB1.Transaction(func(tx *gorm.DB) error {
		if err = tx.Omit("password").Save(&in).Error; err != nil {
			return err
		}
		if err = tx.Delete(&model.AdminRule{},
			"ptype='g' AND v0=? AND v2=?", in.ID, tools.GConfig.App.Casbin.Tenant).Error; err != nil {
			return err
		}
		if len(in.Roles) > 0 {
			casbins := make([]*model.AdminRule, 0)
			for _, v := range in.Roles {
				casbins = append(casbins, &model.AdminRule{
					Ptype: "g",
					V0:    strconv.Itoa(int(in.ID)),
					V1:    strconv.FormatUint(uint64(v), 10),
					V2:    tools.GConfig.App.Casbin.Tenant,
				})
			}
			if err = tx.CreateInBatches(casbins, 100).Error; err != nil {
				return err
			}
		}
		return nil
	})
	return
}

func (impl *adminUserService) DeleteByIdList(idList []uint) (err error) {
	err = db.GDB1.Transaction(func(tx *gorm.DB) error {
		if err = tx.Delete(&model.AdminUser{}, "id in ?", idList).Error; err != nil {
			return err
		}
		if err = tx.Delete(&model.AdminRule{}, "ptype='g' AND v0 in ?", idList).Error; err != nil {
			return err
		}
		// TODO menu
		return nil
	})
	return
}

func (impl *adminUserService) GetById(id uint) (result *dout.AdminUserOut, err error) {
	err = db.GDB1.Omit("password").Take(&result, "id=?", id).Error
	if err != nil {
		return nil, err
	}

	var roles = make([]*dout.AdminRoleOut, 0)
	if err = db.GDB1.Raw(`
	select * from admin_role where id in (
	select v1 from admin_rule where ptype = 'g' and v0=?
	)`, result.ID).Scan(&roles).Error; err != nil {
		return
	}
	result.Roles = roles
	return
}

func (impl *adminUserService) GetByUsername(username string) (result *model.AdminUser, err error) {
	err = db.GDB1.Take(&result, "username=?", username).Error
	return
}

func (impl *adminUserService) ResetPassword(in *din.ResetPassword) error {
	m := dao.Use(db.GDB1).AdminUserOut
	do := m.WithContext(context.Background())

	newPasswordEncrypt, err := tools.GeneratePassword(in.PasswordNew)
	if err != nil {
		return err
	}

	_, err = do.Where(m.ID.Eq(in.ID)).Update(m.Password, newPasswordEncrypt)
	if err != nil {
		return err
	}
	return err
}

func (impl *adminUserService) GetTable(in din.AdminUserTableIn) (rows []*dout.AdminUserOut, total int64, err error) {
	q := dao.Use(db.GDB1).AdminUserOut
	do := q.WithContext(context.Background())

	condList := make([]gen.Condition, 0)
	if in.Enable > 0 {
		condList = append(condList, q.Enable.Eq(in.Enable))
	}
	if in.Word != "" {
		word := fmt.Sprintf("%%%s%%", in.Word)
		do = do.
			Where(do.Where(q.Username.Like(word)).
				Or(q.Name.Like(word)).
				Or(q.Phone.Like(word)))
	}
	rows, total, err = do.Omit(q.Password).
		Where(condList...).
		FindByPage(in.Offset(), in.PageSize)

	for _, v := range rows {
		var roles = make([]*dout.AdminRoleOut, 0)
		if err = db.GDB1.Raw(`
		select * from admin_role where id in (
		select v1 from admin_rule where ptype = 'g' and v0=?
		)`, v.ID).Scan(&roles).Error; err != nil {
			return
		}
		v.Roles = roles
	}
	return
}

func (impl *adminUserService) GetList() (rows []*dout.AdminUserOut, err error) {
	q := dao.Use(db.GDB1).AdminUserOut
	do := q.WithContext(context.Background())

	rows, err = do.Find()
	return
}
